package com.easypan.Service.impl;

import com.easypan.Service.FileInfoService;
import com.easypan.Service.UserService;
import com.easypan.component.RedisUtils;
import com.easypan.component.redisComponent;
import com.easypan.controller.CommonFileController;
import com.easypan.entity.configA.cofig;
import com.easypan.entity.constants.Constants;
import com.easypan.entity.dto.DownloadFileDto;
import com.easypan.entity.dto.SessionwebUserDto;
import com.easypan.entity.dto.UploadResultDto;
import com.easypan.entity.dto.UserSpaceDto;
import com.easypan.entity.po.FileInfo;

import com.easypan.entity.query.FileInfoQuery;
import com.easypan.entity.query.SimplePage;
import com.easypan.entity.vo.PaginationResultVO;
import com.easypan.entity.vo.R;
import com.easypan.enums.*;
import com.easypan.exception.BusinessException;
import com.easypan.mapper.FileInfoMapper;
import com.easypan.util.DateUtil;
import com.easypan.util.ProcessUtils;
import com.easypan.util.ScaleFilter;
import com.easypan.util.StringTools;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;



@Service
public class FileInfoServiceImpl implements FileInfoService {
    /**
     @Resource
     private FileInfoMapper<FileInfo, FileInfoQuery> fileInfoMapper;  * 根据条件查询列表
     */
@Resource
private RedisUtils redisUtils;
    @Resource
    private FileInfoMapper<FileInfo, FileInfoQuery> fileInfoMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R changeFileFolder(String fileIds, String filePid, SessionwebUserDto webUserDto) {
        //给了一个文件的ID +文件要保存的pid位置 --我的想法 拿到文件Id--改pid --
        //目录下也有文件
       //从seeion user+file --来判断一下是否真的有
        if (fileIds.equals(filePid)) {
            //已经在当前目录下还尝试移动时,抛出异常
            throw  new BusinessException(ResponseCodeEnum.CODE_600);

        }
        //如果不是目录文件就无法接到这个异常 --根据测试 ->前端对这个进行了处理了的

        //如果需要移动的目录不是根目录

        FileInfoQuery fileInfoQuery=new FileInfoQuery();
        fileInfoQuery.setUserId(webUserDto.getUserId());
        ///注意ids可能有很多 我们需要切分
        String[] fileArray = fileIds.split(",");
         fileInfoQuery.setFileIdArray(fileArray);
        List<FileInfo> fileInfos = fileInfoMapper.selectList(fileInfoQuery);
////// /**
//     * 根据FileIdAndUserId更新
//     */
        for(int i=0;i<fileInfos.size();i++)
        {
            FileInfo fileInfo=fileInfos.get(i);

            fileInfo.setFilePid(filePid);
            fileInfo.setLastUpdateTime(new Date());
            this.fileInfoMapper.updateByFileIdAndUserId(fileInfo,fileInfo.getFileId(), String.valueOf(webUserDto.getUserId()));
        }

        return R.success("移动成功");
    }

    @Override
    public List<FileInfo> findListByParam(FileInfoQuery param) {
        return this.fileInfoMapper.selectList(param);
    }
@Autowired
private cofig cofig;

    @Resource
public redisComponent redisComponent;
    /**
     * 根据条件查询列表
     */
    @Autowired
   private UserService userService;
    @Resource
    @Lazy
    private  FileInfoServiceImpl fileInfoService;
    @Override
    public Integer findCountByParam(FileInfoQuery param) {
        return this.fileInfoMapper.selectCount(param);
    }

    /**
     * 分页查询方法
     */
    @Override
    public PaginationResultVO<FileInfo> findListByPage(FileInfoQuery param) {
        int count = this.findCountByParam(param);
        int pageSize = param.getPageSize() == null ? PageSize.SIZE15.getSize() : param.getPageSize();

        SimplePage page = new SimplePage(param.getPageNo(), count, pageSize);
        param.setSimplePage(page);
        List<FileInfo> list = this.findListByParam(param);
        PaginationResultVO<FileInfo> result = new PaginationResultVO(count, page.getPageSize(), page.getPageNo(), page.getPageTotal(), list);
        return result;
    }

    /**
     * 新增
     */
    @Override
    public Integer add(FileInfo bean) {
        return this.fileInfoMapper.insert(bean);
    }

    /**
     * 批量新增
     */
    @Override
    public Integer addBatch(List<FileInfo> listBean) {
        if (listBean == null || listBean.isEmpty()) {
            return 0;
        }
        return this.fileInfoMapper.insertBatch(listBean);
    }

    /**
     * 批量新增或者修改
     */
    @Override
    public Integer addOrUpdateBatch(List<FileInfo> listBean) {
        if (listBean == null || listBean.isEmpty()) {
            return 0;
        }
        return this.fileInfoMapper.insertOrUpdateBatch(listBean);
    }

    /**
     * 根据FileIdAndUserId获取对象
     */
    @Override
    public FileInfo getFileInfoByFileIdAndUserId(String fileId, String userId) {
        return this.fileInfoMapper.selectByFileIdAndUserId(fileId, userId);
    }

    /**、
     * 根据FileIdAndUserId修改
     */
    @Override
    public Integer updateFileInfoByFileIdAndUserId(FileInfo bean, String fileId, String userId) {
        return this.fileInfoMapper.updateByFileIdAndUserId(bean, fileId, userId);
    }

    /**
     * 根据FileIdAndUserId删除
     */
    @Override
    public Integer deleteFileInfoByFileIdAndUserId(String fileId, String userId) {
        return this.fileInfoMapper.deleteByFileIdAndUserId(fileId, userId);
    }

    @Override
    @Transactional
    public R updat(SessionwebUserDto sessionKey, String fileId, MultipartFile file,
                   String fileName, String filePid, String fileMd5,
                   Integer chunkIndex, Integer chunks, HttpSession session) {
        UploadResultDto resultDto = new UploadResultDto();


        File tempFileFolder = null;
        Date curTime = new Date();

        //判断一下
        if (sessionKey == null) {
            R.error("用户错误");
        }
        int userid = sessionKey.getUserId();
        //1.修改fileID ->给定一个随机值
        if (StringTools.isEmpty(fileId)) {
            fileId = StringTools.getRandomNumber(Constants.LENGTH_10);
        }
        resultDto.setFileId(fileId);
        /*
        获取可使用的空间
         */
        //第一分片
        UserSpaceDto userSpaceDto = redisComponent.getUserSpaceUsekj(sessionKey.getUserId());
        if (chunkIndex == 0) {
            //查询是否有对应的MD5
            FileInfoQuery infoQuery = new FileInfoQuery();
            infoQuery.setFileMd5(fileMd5);
            infoQuery.setSimplePage(new SimplePage(0, 1));//拿出第一个请求
            infoQuery.setStatus(FileStatusEnums.USING.getStatus());//要转码成功的
            List<FileInfo> dbFileInfo = this.fileInfoMapper.selectList(infoQuery);
            if (!dbFileInfo.isEmpty())//服务器有对应数据
            {
                FileInfo fileInfo = dbFileInfo.get(0);
                //秒传也是插入数据
                //TOOD :此处要判断文件大小--直接插入 --需要改变的东西 userid 文件Id 文件名字重复问题

                fileInfo.setFileId(fileId);
                fileInfo.setFilePid(filePid);
                fileInfo.setUserId(userid);
                fileInfo.setCreateTime(new Date());
                fileInfo.setLastUpdateTime(new Date());
                fileInfo.setStatus(FileStatusEnums.USING.getStatus());
                fileInfo.setDelFlag(FileDelFlagEnums.USING.getFlag());

                fileInfo.setFileName(fileName);

                //判断一下用户空间能不能上传到这个文件
                ///该文件 +使用的空间+ --总空间大小---redis 还是的存才行 不然BUG太太多了

                if (userSpaceDto.getUseSpace() + fileInfo.getFileSize() > userSpaceDto.getTotalSpace()) {
                    return R.error("空间不足,请稍后重试");
                }


                fileInfoMapper.insert(fileInfo);
                resultDto.setStatus(UploadStatusEnums.UPLOAD_SECONDS.getCode());
                //更新用户实时空间
                //直接删除redis 数据  让他们再次sql和redis再次保持一致性

                redisUtils.delete(Constants.REDIS_KEY_USER_SPACE_USE + sessionKey.getUserId());
                //文件重命名 --MD5
                return R.success(resultDto);
            }//不秒传
        }
        ///开始写上传逻辑 --分片上传
        //1.判断大小
        Long currentTempSize = redisComponent.getFileTempSize(sessionKey.getUserId(), fileId);
        if (file.getSize() + currentTempSize + userSpaceDto.getUseSpace() > userSpaceDto.getTotalSpace()) {
            return   R.error("空间不足");
        }
        ///后续要将redis->临时和 file.getsize 大小写进去
        //2.创建分片存储位置
        String tempFolderName=cofig.getProjectFolder()+ Constants.FILE_FOLDER_TEMP;
        //在获取每一个文件自己的分片上传文件夹，这个文件夹对于每个文件来说是唯一的
        String currentUserFolderName = sessionKey.getUserId() + fileId;
        //3.打开这个文件夹 --如果没有咋们就创建
        tempFileFolder = new File(tempFolderName + currentUserFolderName);
        if(!tempFileFolder.exists())
        {
            tempFileFolder.mkdirs();
        }
        //根据当前分片命名
        File file1=new File(tempFileFolder+"/" +chunkIndex);

        //上传
        try {
            file.transferTo(file1);
        } catch (IOException e) {
           return  R.error("上传失败");
        }
        //保存临时文件大小 10 -10
        if(chunkIndex<chunks-1)
        {
            //还需要继续分片上传
            resultDto.setStatus(UploadStatusEnums.UPLOADING.getCode());
            redisComponent.saveFileTempSize(String.valueOf(sessionKey.getUserId()),fileId,file.getSize());
              return R.success(resultDto);
        }
      //返回成功逻辑 --最后一次分片逻辑
        redisComponent.saveFileTempSize(String.valueOf(sessionKey.getUserId()),fileId,file.getSize());
       //--分片合成
       //1.设计数据库的更新 ->文件表 ->用户表
        //2.redis的数据更新
        ///数据库直播
        //
        //取出后缀 后面需要拿这个去
        String fileSuffix=StringTools.getFileSuffix(fileName);
        //fileid+userid+后缀 --文件真实磁盘名字
        String realFilename=currentUserFolderName+fileSuffix;
        //读取文件类型
        FileTypeEnums fileTypeEnums=FileTypeEnums.getFileTypeBySuffix(fileSuffix);
        //这是按月份进行合并，拿到月份
        String month = DateUtil.format(new Date(), DateTimePatternEnum.YYYYMM.getPattern());
        //入库
        FileInfo fileInfo = new FileInfo();
        fileInfo.setFileId(fileId);
        fileInfo.setUserId(sessionKey.getUserId());
        fileInfo.setFileMd5(fileMd5);
        fileInfo.setFilePid(filePid);
        ///咋们redis中 如今保存的就是该文件所有分片的合计大小 我们从redis中拿
        Long fileSizeFromRedis = redisComponent.getFileSizeFromRedis(Constants.REDIS_KEY_USER_FILE_TEMP_SIZE + sessionKey.getUserId() + fileId);
        fileInfo.setFileSize(fileSizeFromRedis);

        fileInfo.setFileName(fileName);
        ///->指向文件的真实路径
        fileInfo.setFilePath(month + "/" + realFilename);

        fileInfo.setCreateTime(curTime);
        fileInfo.setLastUpdateTime(curTime);
        //通过枚举确实是一个非常非常好的方式
         fileInfo.setFolderType(FileFolderTypeEnums.FILE.getType());

         fileInfo.setFileCategory(fileTypeEnums.getCategory().getCategory());
        fileInfo.setFileType(fileTypeEnums.getType());
        fileInfo.setStatus(FileStatusEnums.TRANSFER.getStatus());
        fileInfo.setDelFlag(FileDelFlagEnums.USING.getFlag());

        this.fileInfoMapper.insert(fileInfo);

        ///上传完后 ->空间的操作 -删除redis->再让他计算一次user
        redisUtils.delete(Constants.REDIS_KEY_USER_SPACE_USE + sessionKey.getUserId());
        userService.getUserSpace(session);
        resultDto.setStatus(UploadStatusEnums.UPLOAD_FINISH.getCode());
        //必须让事务提交后才能执行异步 免得查无此人

        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                fileInfoService.transFerFile(fileInfo.getFileId(),sessionKey);
            }
        });
        ///视频等等等的处理



        return R.success(resultDto);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R newFoloder(HttpSession session, String filePid, String fileName) {
        //创建目录 -不能同名字 +pid层级创建 -- session
        SessionwebUserDto sessionwebUserDto= (SessionwebUserDto) session.getAttribute("session_key");

        checkFileName(filePid, String.valueOf(sessionwebUserDto.getUserId()),fileName,1);
     FileInfo file=new FileInfo();
     file.setUserId(sessionwebUserDto.getUserId());
      file.setFilePid(filePid);
        file.setFileId(StringTools.getRandomString(Constants.LENGTH_10));
      file.setFileName(fileName);
   file.setCreateTime(new Date());
   file.setLastUpdateTime(new Date());
   file.setFolderType(1);
        file.setFilePath(null);
        file.setDelFlag(2);
        file.setStatus(2);
        this.fileInfoMapper.insert(file);

        /*

        //为了防止并发过程中出现问题，更新完数据库之后再进行校验
        FileInfoQuery fileInfoQuery = new FileInfoQuery();
        fileInfoQuery.setFilePid(filePid);
        fileInfoQuery.setUserId(userId);
        fileInfoQuery.setFileName(folderName);
        fileInfoQuery.setFolderType(FileFolderTypeEnums.FOLDER.getType());
        fileInfoQuery.setDelFlag(FileDelFlagEnums.USING.getFlag());
        Integer count = this.fileInfoMapper.selectCount(fileInfoQuery);
        if (count > 1) {
            throw new BusinessException("文件夹" + folderName + "已经存在");
        }
        fileInfo.setFileName(folderName);
        fileInfo.setLastUpdateTime(curDate);
         */
return R.success(file);

    }

    public R rename(SessionwebUserDto webUserDto, String fileId, String fileName) {
      FileInfo file=new FileInfo();
      //查询一下
        if(webUserDto==null)
        {
            //用户错误
            return R.error("用户错误");
        }
        ///通过userid+fileid ->文件 -在进行修改 免得被伪造seiion 然后权限出错
        FileInfo fileInfo = fileInfoMapper.selectByFileIdAndUserId(fileId, String.valueOf(webUserDto.getUserId()));
       if(fileInfo==null)
       {
           return R.error("文件错误");
       }
       if(fileInfo.getFileName().equals(fileName))
       {
           return R.success("修改完成");
       }
        String filePid = fileInfo.getFilePid();

       ////传来的是目录还是文件 --因为我们传来的是没有后缀的新名字
        if(FileFolderTypeEnums.FILE.getType().equals(fileInfo.getFolderType()))
        {
            //文件
            fileName=fileName+StringTools.getFileSuffix(fileInfo.getFileName());

        }
        //开始入库了
        Date date=new Date();
        FileInfo dbInfo=new FileInfo();
        dbInfo.setFileName(fileName);
        dbInfo.setLastUpdateTime(date);
        this.fileInfoMapper.updateByFileIdAndUserId(dbInfo,fileId, String.valueOf(webUserDto.getUserId()));


        //为了防止出错 我们查->
        FileInfoQuery fileInfoQuery = new FileInfoQuery();
        fileInfoQuery.setFilePid(filePid);
        fileInfoQuery.setUserId(webUserDto.getUserId());
        fileInfoQuery.setFileName(fileName);
        fileInfoQuery.setDelFlag(FileDelFlagEnums.USING.getFlag());

        Integer count = this.fileInfoMapper.selectCount(fileInfoQuery);
        if (count > 1) {
            throw new BusinessException("文件名" + fileName + "已经存在");
        }
        fileInfo.setFileName(fileName);
        fileInfo.setLastUpdateTime(date);
        return R.success(fileInfo);

    }

    @Override
    public R loadAllFolder(SessionwebUserDto webUserDto, String filePid, String currentFileIds) {
       FileInfoQuery fileInfoQuery=new FileInfoQuery();
       //设置查询的条件 --通过时间倒序
        fileInfoQuery.setOrderBy("create_time desc");
        fileInfoQuery.setUserId(webUserDto.getUserId());
      fileInfoQuery.setStatus(FileStatusEnums.USING.getStatus());
      fileInfoQuery.setFolderType(FileFolderTypeEnums.FOLDER.getType());
      fileInfoQuery.setFilePid(filePid);
      if(!StringTools.isEmpty(currentFileIds))
      {
          ///o4lxjztfYX,yFKPxNy6yZ 用,分割一下
          //可以猜测该方法是用于设置排除文件ID的数组。
          //
          //具体而言，该方法可能会接收一个文件ID的数组作为参数，在后续的操作中排除这些文件ID所对应的文件
          //这个设置的话 ->他只能递归的层数会极少 。
          ///fileInfoQuery.setExcludeFileIdArray(currentFileIds.split(","));

      }
        List<FileInfo> listByParam = fileInfoService.findListByParam(fileInfoQuery);
        return R.success(listByParam);
    }

    @Override
    public R createDownloadUrl(String fileId, SessionwebUserDto webUserDto) {
       //思路 --为了防止盗刷 对方拿到下载连接一直放在那 -
        //1.放进redis
        FileInfo fileInfo=fileInfoMapper.selectByFileIdAndUserId(fileId, String.valueOf(webUserDto.getUserId()));

        //2.如果是目录
        if(fileInfo.getFolderType().equals(FileFolderTypeEnums.FOLDER.getType()))
        {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        /// 生成code+真实数据 放进redis
        String code = StringTools.getRandomString(Constants.LENGTH_50);
        ///    private String downloadCode;
        //    private String fileId;
        //    private String fileName;
        //    private String filePath;
        DownloadFileDto downloadFileDto=new DownloadFileDto();
        downloadFileDto.setFileId(fileInfo.getFileId());
        downloadFileDto.setFileName(fileInfo.getFileName());
        downloadFileDto.setFilePath(fileInfo.getFilePath());
        downloadFileDto.setDownloadCode(code);
        //存入redis
        redisComponent.saveDownloadFile(code,downloadFileDto);
        return R.success(code);
    }

    @Override
    public R createDownloadUrl(String fileId, String userid) {
        //思路 --为了防止盗刷 对方拿到下载连接一直放在那 -
        //1.放进redis
        FileInfo fileInfo=fileInfoMapper.selectByFileIdAndUserId(fileId, String.valueOf(userid));

        //2.如果是目录
        if(fileInfo.getFolderType().equals(FileFolderTypeEnums.FOLDER.getType()))
        {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        /// 生成code+真实数据 放进redis
        String code = StringTools.getRandomString(Constants.LENGTH_50);
        ///    private String downloadCode;
        //    private String fileId;
        //    private String fileName;
        //    private String filePath;
        DownloadFileDto downloadFileDto=new DownloadFileDto();
        downloadFileDto.setFileId(fileInfo.getFileId());
        downloadFileDto.setFileName(fileInfo.getFileName());
        downloadFileDto.setFilePath(fileInfo.getFilePath());
        downloadFileDto.setDownloadCode(code);
        //存入redis
        redisComponent.saveDownloadFile(code,downloadFileDto);
        return R.success(code);
    }

    @Override
    public R download(HttpServletRequest request, HttpServletResponse response, String code) throws UnsupportedEncodingException {
       //判断code ---对了就让下载
///webUserDto->摆设
        DownloadFileDto downloadFile = (DownloadFileDto) redisComponent.getDownloadFile(code);

        if(downloadFile==null)
        {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }

        CommonFileController CommonFileController=new CommonFileController();
        //通过downloadFileDto中的文件路径获取到需要下载的文件地址  ->文件真实路径
        String path=  cofig.getProjectFolder() + Constants.FILE_FOLDER_FILE+downloadFile.getFilePath();
        //获取名字 ->
        String fileName = downloadFile.getFileName();
        response.setContentType("application/x-msdownload; charset=UTF-8");
        if (request.getHeader("User-Agent").toLowerCase().indexOf("msie") > 0) {//IE浏览器
            fileName = URLEncoder.encode(fileName, "UTF-8");
        } else {
            fileName = new String(fileName.getBytes("UTF-8"), "ISO8859-1");
        }
        response.setHeader("Content-Disposition", "attachment;filename=\"" + fileName + "\"");
        try {
            CommonFileController.readFile(path, response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return R.success("正下载");
    }


    @Override
    public R delFile(SessionwebUserDto webUserDto, String fileIds) {
       //删除 -分为目录-和普通文件
        //普通文件好处理 //目录怎么办
        FileInfoQuery fileInfoQuery=new FileInfoQuery();
        fileInfoQuery.setUserId(webUserDto.getUserId());
        String[] fileIdArray = fileIds.split(",");
        fileInfoQuery.setFileIdArray(fileIdArray);
        fileInfoQuery.setDelFlag(FileDelFlagEnums.USING.getFlag());
        List<FileInfo> fileInfos = fileInfoMapper.selectList(fileInfoQuery);
        if(fileInfos.isEmpty())
        {
            return R.error("没有要删除的文件");
        }
        //这里需要一个遍历操作,因为删除的可能是一个目录即文件夹,使用递归进行遍历
        //里面存放的是该目录下的所有文件
        List<String> delFilePidList = new ArrayList<>();
        for (FileInfo file : fileInfos) {
            findAllSubFolderFileIdList(delFilePidList, webUserDto.getUserId(), file.getFileId(), FileDelFlagEnums.USING.getFlag());
        }


        //先不关目录了 普通文件放入回收站--delFilePidList 存放这文件ID
        FileInfo fileInfo=new FileInfo();

      for(int i=0;i<delFilePidList.size();i++)
      {

          String file=delFilePidList.get(i);
          FileInfo fileInfo1 = fileInfoMapper.selectByFileIdAndUserId(file, String.valueOf(webUserDto.getUserId()));
          fileInfo1.setDelFlag(1);
          if(fileInfo1==null)
          {
              break;
          }
          fileInfoMapper.updateByFileIdAndUserId(fileInfo1,file, String.valueOf(webUserDto.getUserId()));
      }
        return R.success("成功");
    }

    @Override
    public R loadRecycleList(SessionwebUserDto webUserDto, String pageNo, String pageSize) {
      FileInfoQuery fileInfoQuery=new FileInfoQuery();
      fileInfoQuery.setUserId(webUserDto.getUserId());
      fileInfoQuery.setDelFlag(1);
   if(StringTools.isEmpty(pageNo)||StringTools.isEmpty(pageNo))
   {
       PaginationResultVO fileInfos =  this.findListByPage(fileInfoQuery);

       return R.success(fileInfos);
   }

      fileInfoQuery.setPageNo(Integer.valueOf(pageNo));
      fileInfoQuery.setPageSize(Integer.valueOf(pageSize));
        PaginationResultVO  fileInfos =this.findListByPage(fileInfoQuery);
        return R.success(fileInfos);
    }

    @Override
    public R saveShare(String fileId, String shareFileIds, String myFolderId, String shareUserId, int userId) {
        return R.error("抱歉 自己下载了上传 懒得写");
    }

    private void findAllSubFolderFileIdList(List<String> delFilePidList, int userId, String fileId, Integer flag) {

        //fileIdList这个集合中存放的包括目录跟文件,因为删除的操作,可以对文件跟目录进行删除
        //因此一开始就将文件id放到这个集合中,就是为了防止在递归不到不是目录的文件
        delFilePidList.add(fileId);
        //进行查询条件的设置,需要对目录文件进行遍历,不是目录文件就跳过
        FileInfoQuery infoQuery  = new FileInfoQuery();
        infoQuery.setUserId(userId);
        infoQuery.setFolderType(FileFolderTypeEnums.FOLDER.getType());
        infoQuery.setFilePid(fileId);
        infoQuery.setDelFlag(flag);
        List<FileInfo> fileInfoList = this.fileInfoService.findListByParam(infoQuery);
        //这里就是递归,自己调用自己
        for (FileInfo fileInfo : fileInfoList) {
            //这里面的结束条件就是deFlag,不是在使用中的就说明需要删除的文件已经全部放入回收站当中了
            findAllSubFolderFileIdList(delFilePidList, userId, fileInfo.getFileId(), flag);
        }
    }


    private void checkFileName(String filePid, String userId, String fileName, Integer folderType) {
        FileInfoQuery fileInfoQuery = new FileInfoQuery();
        fileInfoQuery.setFolderType(folderType);
        fileInfoQuery.setFileName(fileName);
        fileInfoQuery.setFilePid(filePid);
        fileInfoQuery.setUserId(Integer.parseInt(userId));
        //通过模糊查询去数据库中查找是否存在统计目录下有相同名字的文件
        //把回收站中的排除掉
        fileInfoQuery.setDelFlag(FileDelFlagEnums.USING.getFlag());
        Integer count = this.fileInfoMapper.selectCount(fileInfoQuery);
        if (count > 0) {
            //存在则抛出异常
            throw new BusinessException("此目录下已存在同名文件，请修改名称");
        }
    }
    //异步转码 -  重复代码很多->后续优化
    @Async
   public void transFerFile(String fileId, SessionwebUserDto webUserDto)
   {
       //转码是否成功的标志
       Boolean transferSuccess = true;
       //文件的位置
       String targetFilePath = null;
       //文件封面
       String cover = null;
       FileTypeEnums fileTypeEnum = null;
       //通过文件id和用户 找出对应文件 -
       FileInfo fileInfo = fileInfoMapper.selectByFileIdAndUserId(fileId, String.valueOf(webUserDto.getUserId()));
       try{
           //如果文不存在或者文件状态不是转码中，则直接返回
           if(fileInfo==null || !FileStatusEnums.TRANSFER.getStatus().equals(fileInfo.getStatus()))
           {
               return;
           }

           //找到文件所在的临时目录temp（转码所在的目录地址）
           String tempFolderName=cofig.getProjectFolder()+ Constants.FILE_FOLDER_TEMP;
           //在获取每一个文件自己的分片上传文件夹，这个文件夹对于每个文件来说是唯一的
           String currentUserFolderName = webUserDto.getUserId() + fileId;
           //3.打开这个文件夹 --如果没有咋们就创建
         File fileFolder=new File(tempFolderName+currentUserFolderName);
        if(!fileFolder.exists())
        {
          return;
        }
           String fileSuffix = StringTools.getFileSuffix(fileInfo.getFileName());
         ///将上传的文件按照月份存储 ->后续可以改为每一个userid ->根据id存储 ->文件夹就有点多 TOO:更改点
          String month = DateUtil.format(fileInfo.getCreateTime(), DateTimePatternEnum.YYYYMM.getPattern());

      ///拿出目标目录 -
           String targetFolderName = cofig.getProjectFolder() + Constants.FILE_FOLDER_FILE;
      //打开这个目录
        File targetFolder = new File(targetFolderName + "/" + month);
           if (!targetFolder.exists()) {
               targetFolder.mkdirs();
           }
  ///真实文件名  userid+文件ID
           String realFileName=currentUserFolderName+fileSuffix;

           //真实文件目录合并
           targetFilePath=targetFolder.getPath()+"/"+realFileName;


///合并文件-真实目录 文件有了 --临时目录也有了 我们现在就要写目录的合并了
           //真实目录 +seiionkey+临时目录
         this.union(fileFolder.getPath(),targetFilePath,webUserDto,true,fileInfo.getFileName());

         //如果没问题这个时候已经完成了文件合并
         //根据文件路径来判断对应的->文件格式 ->预览 视频切割
           fileTypeEnum=FileTypeEnums.getFileTypeBySuffix(fileSuffix);
           //如果是视频
           if(FileTypeEnums.VIDEO==fileTypeEnum)
           {
               //文件ID 和真实目录
               cutFile4Video(fileId,targetFilePath);
               //视频处理
               cover = month + "/" + currentUserFolderName + Constants.IMAGE_PNG_SUFFIX;
               //视频生成缩略图，封面 coverPath： e:/java/project/easypan/rear-end/file/month/文件名.png
               String coverPath = targetFolderName + "/" + cover;
               ScaleFilter.createCover4Video(new File(targetFilePath), Constants.LENGTH_150, new File(coverPath));

           }///图片切割
           else if (FileTypeEnums.IMAGE==fileTypeEnum) {
               //列缩图就是文件原名字+_
               cover = month + "/" + realFileName.replace(".", "_.");
               String coverPath = targetFolderName + "/" + cover;//得到列缩图文件名
               //文件+文件宽度+生成的目录
               Boolean created = ScaleFilter.createThumbnailWidthFFmpeg(new File(targetFilePath), Constants.LENGTH_150, new File(coverPath), false);
               //由于像素问题可能生成失败
               if(!created)
               {
                   //生成失败
                   FileUtils.copyFile(new File(targetFilePath),new File(coverPath));
               }


           }

       } catch (IOException e) {
           throw new RuntimeException(e);
       } finally {
///  最后 返回
        //合并文件成功 +封面 --更新数据库 -
           //1.根据userid fileid 拿出 在更新
           FileInfo updateInfo = new FileInfo();
         updateInfo.setFileCover(cover);//cover相对路径
           //状态
           updateInfo.setStatus(transferSuccess ? FileStatusEnums.USING.getStatus() : FileStatusEnums.TRANSFER_FAIL.getStatus());//三元运算符
           fileInfoMapper.updateFileStatusWithOldStatus(fileId, String.valueOf(webUserDto.getUserId()), updateInfo, FileStatusEnums.TRANSFER.getStatus());
       }
   }

    private void cutFile4Video(String fileId, String videoFilePath) {
        //创建同名切片目录，即去掉后缀创建一个文件，就是一个目录
//创建同名切片目录，即去掉后缀创建一个文件，就是一个目录
        File tsFolder = new File(videoFilePath.substring(0, videoFilePath.lastIndexOf(".")));
        if (!tsFolder.exists()) {
            tsFolder.mkdirs();
        }

        /**
         * 这个 ffmpeg 命令用于将一个视频文件进行转换和复制操作。下面是对该命令中各个参数的解释：
         * - `-y`：表示自动覆盖输出文件，如果输出文件已经存在。
         * - `-i %s`：表示输入文件的路径和文件名，`%s` 是一个占位符，需要被实际的文件路径和文件名替换。
         * - `-vcodec copy`：表示视频流的编码格式保持不变，直接复制。
         * - `-acodec copy`：表示音频流的编码格式保持不变，直接复制。
         * - `-vbsf h264_mp4toannexb`：表示对视频进行 H.264 格式的转换，将 MP4 格式转换为 Annex B 格式（一种常用的视频流格式）。
         * 综合起来，该命令的作用是将输入的视频文件进行复制操作，同时将视频流转换为 H.264 Annex B 格式，并将转换后的结果保存到指定的输出文件中。
         * 请注意，`%s` 是一个占位符，实际使用时需要将其替换为实际的文件路径和文件名。此外，该命令还可以根据需要添加其他参数来进行更多的视频处理操作，
         * 例如调整视频的分辨率、帧率等。
         */
        final String CMD_TRANSFER_2TS = "ffmpeg -y -i %s  -vcodec copy -acodec copy  %s";

        /**
         * 这个 ffmpeg 命令用于将一个视频文件进行分段处理，将其切分成多个固定时长的片段。下面是对该命令中各个参数的解释：
         * - `-i %s`：表示输入文件的路径和文件名，`%s` 是一个占位符，需要被实际的文件路径和文件名替换。
         * - `-c copy`：表示对音视频流进行复制操作，不进行重新编码。
         * - `-map 0`：表示将输入文件的所有音视频流都映射到输出文件中。
         * - `-f segment`：表示指定输出格式为分段（segment）格式。
         * - `-segment_list %s`：表示指定分段列表文件的路径和文件名，`%s` 是一个占位符，需要被实际的文件路径和文件名替换。
         * 分段列表文件是一个文本文件，记录了分段文件的信息。
         * - `-segment_time 30`：表示指定每个分段的时长为 30 秒。
         * - `%s/%s_%%4d.ts`：表示指定输出分段文件的路径和文件名的格式，`%s` 是一个占位符，需要被实际的文件路径替换。
         * `%s_%%4d.ts` 是一个占位符，其中 `%%4d` 表示输出文件名中的数字部分采用 4 位的序号。
         *
         * 综合起来，该命令的作用是将输入的视频文件进行分段处理，每个分段的时长为 30 秒，同时生成一个分段列表文件，记录了分段文件的信息。
         * 分段文件的命名格式为 `%s_%%4d.ts`，其中 `%s` 是一个占位符，表示输出文件的路径，`%%4d` 表示输出文件名中的数字部分采用 4 位的序号。
         * 请注意，`%s` 是一个占位符，实际使用时需要将其替换为实际的文件路径和文件名。
         * 此外，该命令还可以根据需要添加其他参数来进行更多的视频处理操作，例如调整视频的分辨率、帧率等。
         */
        final String CMD_CUT_TS = "ffmpeg -i %s -c copy -map 0 -f segment -segment_list %s -segment_time 30 %s/%s_%%4d.ts";
        //这里先将视频文件转成ts文件，在进行切割操作，这样效率会更高tsPath：e:/java/project/easypan/rear-end/file/month/文件名/index.ts
        String tsPath = tsFolder + "/" + Constants.TS_NAME;
        //生成.ts
        String cmd = String.format(CMD_TRANSFER_2TS, videoFilePath, tsPath);
        //执行cmd命令
        ProcessUtils.executeCommand(cmd, false);
        //生成索引文件.m3u8 和切片.ts | M3U8_NAME = "index.m3u8";
        cmd = String.format(CMD_CUT_TS, tsPath, tsFolder.getPath() + "/" + Constants.M3U8_NAME, tsFolder.getPath(), fileId);
        //执行cmd命令
        ProcessUtils.executeCommand(cmd, false);
        //删除index.ts
        new File(tsPath).delete();
    }

    //临时目录 --目标目录 --文件名 --是否删除目录
    private void union(String targetFilePath, String currentUserFolderName, SessionwebUserDto webUserDto,boolean delSource,String fileName ) throws IOException {
    //判断一下转码目录是否存在
        File dir=new File(targetFilePath);
        if(!dir.exists())
        {
            throw new RuntimeException("目录不存在");
        }
  //遍历打开临时目录下的所有文件
        File fileList[]=dir.listFiles();

//打开一下目标目录
        File targetFile=new File(currentUserFolderName);
//too 后续进行 遍历读取 再写入
//文件初始后续再给
        RandomAccessFile writeFile = null;
    try{
        //先给予读写权限
        writeFile=new RandomAccessFile(targetFile,"rw");
        //给一块读写缓冲器
        byte[] b=new byte[1024*10];
        //循环合并 -
      for(int i=0;i<fileList.length;i++) {
          int len = -1;//停止标志;
          //依次打开临时文件中的每个文件

          ///使用 File.separator ->根据操作系统不同代表不同的反斜杠 防止不同系统编译出错 >为什么前面不用这个 修改点+1

          File chunkFile = new File(targetFilePath + File.separator + i);

          //给一个读权限
          RandomAccessFile readFile = null;

          try {
              readFile = new RandomAccessFile(chunkFile, "r");
              //读到文件末尾结束->读到文件长度为-1结束
              while ((len = readFile.read(b)) != -1) {
                  writeFile.write(b, 0, len);//
              }

          } catch (FileNotFoundException e) {
              throw new RuntimeException("读取源文件失败");
          } catch (IOException e) {
              throw new RuntimeException("读取源文件失败");
          }
          finally {
              readFile.close();
          }

      }

    } catch (IOException e) {
        throw new RuntimeException("读写文件失败");
    } finally {
        if (null != writeFile) {
            writeFile.close();
        }
        if (delSource)
        {
            if (dir.exists()) {
                try {
                    ///会递归地删除指定的目录（包括目录中的文件和子目录）以及它们的内容
                    FileUtils.deleteDirectory(dir);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    ////

    }



}
